package Dashboard;

import java.util.ArrayList;
import java.util.Map;

import UserInput.UserInput;

import Command.Command;
import Communication.BoatDataWrapper;
import Gauges.*;

/**
 * This class controls the dashboard frame,
 * reads the data from the boat,
 * converts the data into usable data
 * and makes the dashboard display it
 * 
 * @author Sietse
 */
public class DashboardController
{
	//The frame everything will be displayed in
	private DashboardFrame dashboardFrame;
	
	//The master list of every gauge 
	private ArrayList<Gauges> gaugeList;
	
	//A flag used when starting a replay
	private boolean isReplay;
	
	
	
	//Used for subtracting the sailrotation from the windgaugerotation
	private int sailValue;
	
	/**
	 * The constructor initializes the gauges and puts them in the gaugelist
	 * It also starts up the DashboardFrame
	 */
	public DashboardController()
	{	
		dashboardFrame = new DashboardFrame();
		isReplay = false;
		
		
		gaugeList = new ArrayList<Gauges>();
		gaugeList.add(new AngleGauge(GaugeName.ANGLE));
		gaugeList.add(new ControlGauge(GaugeName.CONTROL));
		gaugeList.add(new HelmGauge(GaugeName.HELM));
		gaugeList.add(new KeelGauge(GaugeName.KEEL));
		gaugeList.add(new MotorGauge(GaugeName.MOTOR));
		gaugeList.add(new SailGauge(GaugeName.SAIL));
		gaugeList.add(new SpeedGauge(GaugeName.SPEED));
		gaugeList.add(new WindDirectionGauge(GaugeName.WIND));
		gaugeList.add(new EmergencySituationGauge(GaugeName.EMERGENCYSITUATION));
		gaugeList.add(new EmergencyStopGauge(GaugeName.EMERGENCYSTOP));
		gaugeList.add(new BluetoothGauge(GaugeName.BLUETOOTH));
	}
	
	/**
	 * Calls the DashboardFrame's writeLineToBox method in order to
	 * set the text of the commandListBox in which the commands are displayed
	 * 
	 * @param p_strLine The string representing the command 
	 * @param p_cmdCommand The Command itself
	 */
	public void writeLineToBox(String p_strLine, Command p_cmdCommand)
	{
		dashboardFrame.writeLineToBox(p_strLine, p_cmdCommand);
	}
	
	/**
	 * Adds the input listeners
	 * @param p_arrUserInput A list of input listeners
	 */
	public void addInputListeners(ArrayList<UserInput> p_arrUserInput)
	{
		dashboardFrame.addInputListeners(p_arrUserInput);
	}
	
	/**
	 * Called by the MainController whenever data from the boat needs to be displayed
	 * @param data a BoatDataWrapper containing boat data
	 */
	public void retreiveBoatInput(BoatDataWrapper data)
	{
		if(!isReplay)
		{
			processBoatInput(data);
		}
	}
		
	/**
	 * Gets data from boat input (through MainController).
	 * Split data and put it into the gauges.
	 * @param data = wrapper for the boat input data.
	 */
	private void processBoatInput(BoatDataWrapper data)
	{
		//Set bluetooth connection light
		dashboardFrame.setBluetoothConnection(data.getBluetoothConnection());
		//Get data from wrapper and put it into gauges.
		for(Gauges gauge : gaugeList) //For every gauge in the gaugelist
		{
			for(Map.Entry<GaugeName, Integer> entry : data.getAllData().entrySet()) //Go through all the gauge data avaliable
			{
				if(gauge.getName().equals(entry.getKey())) //Connect the right data to the right gauge by name
				{
					gauge.setValue(this.rawDataToUsableData(entry.getKey(), entry.getValue())); //Convert the raw data and put it into the gauge
					break; 
				}
			}
		}	
		//Clear the BoatDataWrapper's hashmap, which contains all data, so it can be re-used
		data.clearHashMap();	
		//Call the Frame to update the gauges, providing the gaugelist
		dashboardFrame.updateGauges(this.gaugeList);
	}
		
	/**
	 * Gets the list of sessions from the database and calls the Frame to populate 
	 * the replaylist with the sessions
	 * @param sessionList the list of sessions from the database
	 */
	public void retreiveSessionIds(ArrayList<Integer> sessionList)
	{
		dashboardFrame.populateReplayList(sessionList);
	}

	/**
	 * Create replay from given data
	 * @param data
	 */
	public void createReplay(ArrayList<BoatDataWrapper> replay, int snapshotMultiply)
	{
		long timeStamp = 0;
		
		
		
		//flag while replay is played. All other data will be ignored.
		dashboardFrame.setReplayLight((isReplay = true));
		
		//iterate through the arraylist of boatdatawrappers.
		for(BoatDataWrapper data : replay)
		{
			//When timestamp is other than zero.
			if (timeStamp != 0)
			{
				try 
				{					
					//Sleep for a x number of seconds. This is the difference between the two timestamps divided by 10.
					Thread.sleep((data.getTimeStamp() - timeStamp) / snapshotMultiply);
					//Thread.sleep(frequency);
				} 
				catch (InterruptedException e) 
				{
					e.printStackTrace();
				}
			}
			//store previous timestamp
			timeStamp = data.getTimeStamp();
			
			//process the datawrapper to the view.	
			this.processBoatInput(data);
		}	
		
		//give flag free for other data.
		dashboardFrame.setReplayLight((isReplay = false));
	}
	
	/**
	 * @return the list of gauges
	 */
	public ArrayList<Gauges> getGaugeList()
	{
		return gaugeList;
	}

	/**
	 * Converts the raw data from the boat into useable data for the dashboard
	 * @param gaugeName the gauge from which the data is coming
	 * @param value the value of the gauge from which the data is coming
	 * @return a call to a method which returns useable data
	 */
	private int rawDataToUsableData(GaugeName gaugeName, int value)
	{
		switch(gaugeName)
		{
			case ANGLE: return calculateAngle(value);
			case HELM: return calculateHelm(value);
			case CONTROL: return calculateControl(value);
			case SAIL:  this.sailValue = value;
						return calculateSail(value);
			case KEEL: return calculateKeel(value);
			case SPEED: return calculateSpeed(value);
			case WIND: return calculateWind(value);
			case MOTOR: return calculateMotor(value);
			case EMERGENCYSITUATION: return value;
			case EMERGENCYSTOP: return value;
			case BLUETOOTH: return value;
		}
		throw new AssertionError("Unknown op: " + gaugeName);
	}
	
	private int calculateAngle(int value)
	{
		int corrected = value - 101;
		return BitToDegreesCorrected(corrected);
	}
	
	private int calculateHelm(int value)
	{
		return BitToDegreesCorrected(value);
	}
	
	private int calculateSail(int value)
	{
		return BitToDegreesCorrected(value);
	}
	
	private int calculateSpeed(int value)
	{
		return value;
	}
	
	private int calculateWind(int value)
	{
		int correction = 0;
		if (sailValue <= 0)
			correction = 64;
		else if (sailValue >= 255)
			correction = -64;
		else
			correction = 64 + (sailValue / 2);
		
		System.out.println("wind position value:" + value + " correction:" + correction);
		return BitToDegrees(value + correction);
	}
	
	private int calculateMotor(int value)
	{
		return value;
	}
	
	private int calculateControl(int value)
	{
		return value;
	}
	
	private int calculateKeel(int value)
	{
		return value;
	}
	
	/**
	 * Converts a value in the range of 0-255 to a value in range of 0-360
	 * @param eightBitValue the 8-bit value originally from the boat
	 * @return the number of degrees expressed by the 8-bit value
	 */
	private int BitToDegrees(int eightBitValue)
	{
		float flt = ((360.0f / 255.0f) * eightBitValue);
		
		int result = Math.round(flt);
		
		return result;
	}
	
	private int BitToDegreesCorrected(int eightBitValue)
	{
		float flt = (90.0f + ((180.0f/255.0f) * eightBitValue));
		return Math.round(flt);
	}
	
	
}